丝般顺滑!全新垃圾回收器 ZGC 初体验 | 龙蜥技术
Java GC简介
垃圾回收 GC 是 Java 语言的自动内存管理机制,能够自动销毁垃圾对象(不再能够被引用的对象),从而释放内存以供后续使用。在 GC 的帮助下,Java 开发者只需专注在自身业务逻辑,调用 new 语句创建对象,而无需编写销毁对象的语句,从而提高了代码开发效率和代码质量。
GC性能指标
GC 会降低吞吐率(QPS上限)。GC 线程占用额外的 CPU 资源,从而影响 Java 线程使用 CPU 的份额。人们往往认为 GC 必然与暂停相生相伴。然而此观点不完全正确。现代 Java 的 GC 可以启动并发 GC 线程与 Java 线程并发执行。
丝般顺滑的ZGC体验
Parallel GC:吞吐率高,GC 暂停时间长;
G1 GC,CMS GC:吞吐率和 GC 暂停时间都比较好,G1 GC 是 Java11 的默认 GC(目标 GC 暂停时间为 200ms),而 CMS GC 在 Java11 中已经不推荐使用;
ZGC,Shenandaoh GC:GC 暂停时间短,吞吐率一般。
本文的主角 ZGC 是 OpenJDK11 引入的新一代 GC,暂停时间能够保持在 10ms 以内,且最高能支持 TB 级别的大堆。
Java11 之前的 GC 通常需要 100ms 以上的暂停,会给 RT P99 等指标带来负面影响,让运行中的 Java 业务仿佛在坑坑洼洼的道路上磕磕绊绊地前行。毫秒级别暂停的 ZGC 能够让 RT P99 进一步下降,运行中的 Java 业务得了到丝般顺滑的体验。ZGC 大多数情况下只需要调节堆的大小和并发 GC 线程数量,调优较为容易,大大节约心智成本。
ZGC 实践
本节在 ZGC 投入实战之前首先介绍了 ZGC 的适用场景,目的是让业务上线之前能够选择正确的 GC。我们在充分评估业务特点的基础上,让相应的业务运行在 ZGC 上,取得了 RT 的提升。然而 OpenJDK 11 的 ZGC 尚处于实验性阶段,我们在实践中遇到了一些问题。我们将这些问题作为风险项记录下来,并在 Dragonwell 11 中尝试解决这些问题。
ZGC 适用场景
ZGC 取得了卓越的毫秒级暂停性能,然而副作用是 ZGC 可能降低业务吞吐率( ZGC 项目主页声称损失至多 15% 的吞吐率)。其原因主要包括三个方面:
1.Java11 的 ZGC 是单代 GC,每一轮 ZGC 均需要处理长寿对象(多次 GC 之后依然存活的对象),而 Java11 以前的 GC 均是分代 GC,不需要每次 GC 都处理长寿对象;
2. ZGC 需要开启并发 GC 线程,减少 Java 线程使用 CPU 的份额;
3.ZGC 的读屏障(后文将介绍)使得每个从堆中加载对象的操作都有额外的开销。此外,由于 ZGC 不支持压缩指针技术,ZGC 在 32GB 以内小堆上无法享受压缩指针带来的性能提升。
综合以上的 ZGC 特点的描述,笔者总结了 ZGC 适用场景,供有意切换到 ZGC 的朋友们参考:
1.对长尾请求 RT P99/P999 等指标有高要求的 Java 业务:这些业务通常要求实时响应,对最慢的 1% 或 0.1% 的请求非常敏感;
2.机器的内存与 CPU 资源丰富:丰富的计算资源可以开启更大的堆和更多的并发 GC 线程;
3.可以容忍吞吐率降低:业务经过权衡后,认为 RT P99/P999 的指标比 QPS 指标更重要;
此外,Java 业务如果仍然运行在 Java 8 上,那么还需要考虑到切换到 Java 11 的代价。
ZGC规模化实践
阿里内部许多对长尾请求 RT 有严格要求的 Java 业务,为了突破 GC 暂停对于 RT 的瓶颈,这些 Java 业务逐渐升级到 Java11,并且选择 ZGC 作为 GC。下面展示了阿里内部使用 ZGC 获得 RT 提升的案例。本节提到的Concurrent Mark/Relocate将在本系列第二篇文章中阐述。
1.高性能数据库:Lindorm 是阿里内部高性能 NoSQL HBase 分支。Lindorm 在 ZGC 上稳定运行近两年,期间顺利通过双十一大考。Lindorm 运行期间的 ZGC 暂停时间稳定在 5ms 左右,最大暂停时间不超过 8ms 。ZGC 大大改善了线上运行集群的 RT 与毛刺指标,平均 RT 优化 15%~20%, P999 RT 减少到原先的一半以内。2019 年双十一蚂蚁集群在 ZGC 的加持下, Lindorm RT P999 时间从 12ms 降低到了 5ms 。下图展示了 Lindorm 在 ZGC 上的 GC 暂停表现(单位为微秒)。
3.风控调用:线上部分应用对风控调用耗时敏感。这些应用设置的服务调用超时时间很短(< 50ms ),而目前风控系统一次 Young GC 的耗时在 60ms 左右。只要遇到 GC ,业务调用就会超时。以红包业务为例,超时后红包要么发放不出去,要么发出去但有可能被羊毛党薅走,对业务都是有影响。为了提升可用性,需要 ZGC 的支持。线上实际运行 ZGC 的暂停时间保持在 10ms 以内,能够满足这些 RT 敏感的应用等需求。由于风控系统缓存对象较多,导致 Concurrent Mark 阶段时间较长,影响吞吐率提升。为了支持 ZGC 更流畅地运行,风控系统减少了缓存的长寿对象,从而提高 QPS 上限。
OpenJDK11 ZGC的风险
在以上实践过程的早期,我们采用了 OpenJDK11 的 ZGC ,而该特性仅仅处于实验性阶段。从 OpenJDK 11 发布实验性 ZGC 以来, ZGC 的稳定性得以增强,功能日臻完善。到 OpenJDK 15 发布的时候, ZGC 已经成为生产就绪的特性。OpenJDK 11 是长期支持的版本,而目前发布的 OpenJDK12-16 都不是长期支持的,因此在生产实践中直接部署 OpenJDK 15/16 来尝鲜生产就绪的 ZGC 存在困难。
以上实践表明,对于 10GB 到数百 GB 的 Java 堆,ZGC 的确能将暂停保持在 10ms 以内。然而这些业务均报告“ QPS 上不去”,也就是说对于吞吐型场景, ZGC 表现不理想。在吞吐型场景中, ZGC 的回收速度跟不上分配速度,出现了分配停顿(Allocation Stall),即暂停当前正在创建对象的线程,等待 ZGC 释放空闲空间。另外, Lindorm 还报告小堆上的 ZGC 整体效果甚至没有 G1GC 来得好。此外,以上实践也遇到了一些 OpenJDK11 实验性 ZGC 的问题:
2.最坏情况会发生 OOM :风控业务注意到 ZGC 可能抛出 OOM ,此现象发生在 Concurrent Relocate 阶段。ZGC 通常会预留一段空间供 Concurrent Relocate,然而 JDK11 的 ZGC 代码无法保障预留的空间是足够的,如果对象 Relocate 速度很快,就有可能抛出 OOM 。此问题在 OpenJDK16 中解决。
3.Page Cache Flush 影响 RT :ZGC 把堆分为小/中/大三种规格的 ZPage(不同大小的对象分配到不同类型的 ZPage 中),如果各种大小对象分配速度不稳定(比如中等大小的对象突然变多,那么就需要把小/大的 ZPage 转换成中等 ZPage ,此过程耗时长)。Lindorm 注意到此现象会严重影响 RT。OpenJDK15 的 ZGC 对这个现象有所缓解。
Dragonwell11开启ZGC
Dragonwell11 下载:
https://github.com/alibaba/dragonwell11/releases/tag/dragonwell-11.0.11.7_jdk-11.0.11-ga
Dragonwell ZGC 调优选项:
https://github.com/alibaba/dragonwell11/wiki/Alibaba-Dragonwell-11-Release-Notes#zgc-options
现 DragonWell 已加入 龙蜥社区 (OpenAnolis )Java 语言与虚拟机 SIG,同时龙蜥操作系统(Anolis OS )8 版本支持 DragonWell 云原生 Java ,欢迎大家加入社区 SIG,参与社区共建。
—— 完 ——
加入龙蜥社群
加入微信群:添加社区助理-龙蜥社区小龙(微信:openanolis_assis),备注【龙蜥】拉你入群;加入钉钉群:扫描下方钉钉群二维码。欢迎开发者/用户加入龙蜥OpenAnolis社区交流,共同推进龙蜥社区的发展,一起打造一个活跃的、健康的开源操作系统生态!
Dragonwell钉钉交流群
龙蜥社区钉钉交流群
龙蜥社区(OpenAnolis)是由企事业单位、高等院校、科研单位、非营利性组织、个人等按照自愿、平等、开源、协作的基础上组成的非盈利性开源社区。龙蜥社区成立于2020年9月,旨在构建一个开源、中立、开放的Linux上游发行版社区及创新平台。
短期目标是开发龙蜥操作系统(Anolis OS)作为CentOS替代版,重新构建一个兼容国际Linux主流厂商发行版。中长期目标是探索打造一个面向未来的操作系统,建立统一的开源操作系统生态,孵化创新开源项目,繁荣开源生态。
加入我们,一起打造面向未来的开源操作系统!
Https://openanolis.cn